import 'package:flutter/material.dart';
import 'package:flutter_demo/widgets/app_widgets.dart';

import '../../page_with_title.dart';

///关于Flutter Widget
class WidgetPage extends PageWithTitle {
  const WidgetPage() : super("基础组件(Widget)");

  @override
  List<Widget> buildContentWidgets(BuildContext context) {
    return const [
      PrimaryTitle("简介"),
      BodyText("Flutter UI由Widget、Element、RenderObject构成三层架构，实现高效ui构建、复用、渲染"),
      LabelText("- widgets，flutter sdk中存放基础组件的包，其中框架大部分基础组件定义于basic.dart文件"),
      LabelText("- material，material样式组件，作为安卓开发我肯定是主要看这个的，flutter团队也是着重于material"),
      LabelText("- cupertino，ios那边常用样式组件，对比material真的是肉眼可以见的少，我也不建议在一个方案中使用两种设计"),
      AppSpacer.large,
      UlItem("Widget"),
      BodyText("组件,开发者通过widget描述ui外观与嵌套关系快速构建页面，框架通过runApp构建widget树。"),
      LabelText("- widget描述ui外观属性，本身具有不可变性，当ui变更时，通过构建新的widget来取代旧的widget。"),
      OlItem(1, "构造函数 Key"),
      BodyText(
          "当widget树发生变化时，key用于告知框架新旧widget的对应关系，以便框架在新的位置复用其已经创建的element对象，这种情况常见于动态列表重排序等操作时优化性能、延续组件状态"),
      LabelText("- ValueKey，当数据具有唯一id时，可以将其作为widget的key"),
      LabelText(
          "- ObjectKey，将对象作为key，flutter默认比较是否为同一内存对象，若要比较对象内容是否相等，可借助第三方库equatable实现比较属性值"),
      LabelText(
          "- UniqueKey，直接创建一个内存对象作为key，注意不要在build方法中去创建，这样每次都会是新的对象，即使存储到widget中也无法保证这个widget不会被重建，不太建议使用"),
      LabelText("- GlobalKey，全局key"
          "\n1、一般不建议使用，通过状态管理方案实现跨组件状态同步"
          "\n2、在框架中多用于组件封装后通过key对外提供当前的state对象，让我们可以在build这个组件后，在点击之类的事件触发时，通过key来访问或修改state以达到更新该组件的目的。"
          "\n3、其内部实现是在element mount阶段，将element与对应的key存储到静态map中，从GlobalKey获取state时，它内部自己从map查询当前绑定的element，再从element获取state。"),
      OlItem(2, "createElement"),
      LabelText("创建元素的方法，框架通过这个方法构建element树"),
      OlItem(3, "StatelessWidget、StatefulElement"),
      LabelText("开发者通常通过这两个Widget子类实现页面ui构建，详见这两个类的使用介绍"),
      AppSpacer.large,
      UlItem("Element"),
      BodyText(
          "元素，在Flutter框架中element作为了renderObject与widget的沟通桥梁，element负责持有当前对应的widget并创建renderObject，当widget被新的实例替换时，element负责判断是否可以复用，更新记录的widget和renderObject"),
      OlItem(1, "构造函数 widget"),
      LabelText("在widget创建element时即通过构造函数绑定了当前widget对象"),
      OlItem(2, "update"),
      BodyText(
          "若一片区域的widget发生更新时，此元素的组件也在该区域内，原本的位置迎来了新的widget,通过此方法来更新element绑定的widget。"),
      LabelText(
          "像StatefulWidget、StatelessWidget的element会执行rebuild方法，最终执行到build重新构建自身区域内的所有widget"),
      OlItem(3, "rebuild performRebuild"),
      BodyText(
          "rebuild会执行performRebuild"),
      LabelText("- ComponentElement.performRebuild会执行build重新构建widget"),
      OlItem(4, "markNeedsBuild"),
      BodyText("标记这个元素需要重新构建"),
      LabelText(
          "- 其常见的调用方就是StatefulWidget，我们通过它的state.setState更新组件状态后执行此函数标记元素需要重建。"),
      LabelText(
          "- 其通过owner.scheduleBuildFor将此元素加入_dirtyElements队列，在‌Vsync时通过buildScope函数对_dirtyElements内的所有element执行rebuild操作，实现重建widget树"),
      BodyText("至此我们大致可以看到它是怎么更新widget树的"),
      OlItem(5, "updateChild"),
      BodyText(
          "更新子元素，在ComponentElement.performRebuild执行完build重构widget后，会调用这个函数来更新element"),
      LabelText("当前元素将子元素（旧的）与新创建的子widget传入此方法，它通常有三种结果"),
      LabelText("- 新旧widget是一致的（比如AppSpacer.large就是全局单例），无需更新childElement"),
      LabelText(
          "- 新旧widget的key是一致的，canUpdate判断通过,可以复用element，此时会执行childElement.update(newWidget)更新子元素绑定的组件"),
      LabelText("- 需要更新childElement，执行inflateWidget进行更新"),
      OlItem(6, "inflateWidget"),
      BodyText(
          "填充组件，将childWidget传入，通过createElement创建新的childElement，通过mount方法将其挂载到element树子节点位置，替换原本的childElement"),
      OlItem(7, "生命周期 _lifecycleState"),
      BodyText(
          "由于widget只是描述ui属性并且不可更改，所以element更加等同于我们认知的view对象，widget只是这个view的参数配置对象，理所当然的element也有类似View的attach、death to Window的状态，另外也有类似于可不可见的状态，这些就是element的生命周期"),
      LabelText("- mount函数，这个函数会将元素挂载到element树上，并进入active状态"),
      LabelText(
          "- deactivate函数，这个函数会将元素加入到非活动元素列表，并将其renderObject从render树上移除，进入inactive状态"),
      LabelText(
          "- activate函数，这个函数会将元素从非活动元素列表移除，并将其renderObject重新加入render树，重新进入active状态"),
      LabelText("- unmount函数，执行完这个函数，元素从element树上卸载，进入defunct状态"),
      AppSpacer.large,
      UlItem("RenderObject"),
      BodyText(
          "渲染对象，涉及到渲染，该对象由RenderObjectWidget.createRenderObject创建，保存在element中"),
      LabelText("- 元素挂载时，从当前绑定的widget创建调用createRenderObject创建渲染对象，将其添加到渲染树"),
      LabelText("- 元素卸载时，将渲染对象从渲染树移除，并销毁"),
      LabelText("- 在RenderObjectElement.performRebuild时，通过RenderObjectWidget.updateRenderObject让widget将当前最新的状态更新到renderObject，而renderObject在接收到新的属性设置时，应该自行判断是否需要更新，若需要则自行调用markNeedsPaint或markNeedsLayout"),
      OlItem(1, "adoptChild dropChild"),
      BodyText("子渲染对象的添加和移除"),
      OlItem(2, "visitChildren"),
      BodyText("通过回调方法访问每一个子渲染对象"),
      OlItem(3, "markNeedsLayout"),
      BodyText("请求重新布局，与View的requestLayout类似，最终会调用layout"),
      OlItem(4, "layout performLayout"),
      BodyText("实现布局，以下为layout的实现过程"),
      LabelText("- layout方法会接收来自父级渲染对象设置的尺寸约束constraints（一般是在父级渲染对象的performLayout时调用）"),
      LabelText("- 执行performResize，在RenderBox（2D绘图的RenderObject基类）的实现中会调用computeDryLayout根据尺寸约束计算尺寸，比如在RenderParagraph中就根据文本和画笔设置计算绘制内容在父级约束下的实际布局时的尺寸。"),
      LabelText("- 执行performLayout，实现布局，若包含子渲染对象就会在这里逐个调用其layout方法进行布局"),
      LabelText("- 执行markNeedsPaint请求重绘"),
      OlItem(5, "markNeedsPaint"),
      BodyText("请求重新绘制，与View的postInvalidate类似，在下次vsync会进行绘制"),
      OlItem(6, "paint"),
      BodyText("实现绘制，与View的draw方法类似"),
      LabelText("- 方法接收两个参数，context是上下文，从中可以取得画布进行绘制，offset是绘制的偏移量（这么自由的吗，那如果不做偏移岂不是可以到处画）"),
      LabelText("- 根据偏移位置绘制自身内容"),
      LabelText("- 逐个计算子渲染对象的偏移并绘制"),
      BodyText("有意思，子渲染对象经过计算，根据偏移量绘制自身内容是不会超出父渲染对象的范围的，但如果不根据偏移量进行绘制，应该是有可能盖到其它不需要更新的组件上的，那这样怎么确定需要重新渲染的范围？难道真的全靠自觉？在View系统中ViewGroup默认是会裁切绘制区域防止子组件越界，如果不想裁切得显示设置padding与bounds的裁切属性。后面有空的时候验证一下"),
      OlItem(7, "dispose"),
      BodyText("分离渲染对象，一般在这里进行资源回收，比如渲染资源、动画等异步任务"),
      AppSpacer.large,
      PrimaryTitle("总结"),
      BodyText("从Flutter的三重架构设计看，确实在UI上很有想法"),
      LabelText("- widget专注于UI描述，element专注于状态管理，renderObject专注于布局渲染"),
      LabelText("- element与render的分离，使得布局渲染过程无需再去遍历整个视图树"),
      LabelText(
          "- 据说对于一整块静态的widget，它有渲染缓存，可以将该区域内的所有widget绘制缓存起来，对比那些动画、视频播放需要频繁更新的图层，可以实现并发渲染，提交渲染速度减少渲染计算量"),
      LabelText("- 对比view系统，它不需要将整个视图树重新布局与绘制，局部更新"),
      LabelText(
          "- 对比compose，我觉得确实有些不如，就尺寸、边距、背景色这些来说，compose封装到Modifier，而Flutter连设置个padding都得再套一层widget，嵌套起来属实头大"),
    ];
  }
}
